<?php

class DatabaseException extends Exception {
	/**
	 * Construct the exception
	 *
	 * @param	string $msg
	 * @param	int $code
	 * @param	Exception $previous
	 * @return void
	 */
	public function __construct($msg = null, $code = 0, Exception $previous = null) {
		$msg = $msg ? 'MySQL: '.$msg : '';
		if (version_compare(PHP_VERSION, '5.3.0', '<')) {
			parent::__construct($msg, (int)$code);
		} else {
			parent::__construct($msg, (int)$code, $previous);
		}
	}

	public function __toString() {
		return $this->code . '#	' . $this->message . ' in ' . $this->file . ':' . $this->line . "\nStack Trace:\n" . $this->getTraceAsString() . "\n";
	}
}

/**
 * Class for interacting with MySQL database
 * using mysql libraries for php
 * Static declaration replaced by Factory
 *
 * @author silentium
 * @license GPLv2
 * @global
 */
class MySQL {

	/**
	 * array of connections
	 * @staticvar array
	 */
	private static $instances = array();
	private static $last_instance = null;
	/**
	 * get active database connection handler
	 * @static
	 * @param null|Resourse $database
	 * @return MySQL
	 */
	public static function getInstance($database = null) {
		if ( is_null($database) ) {
			$database = self::$last_instance;
		}
		if ( array_key_exists($database, self::$instances) ) {
			return self::$instances[$database];
		}
		throw new DatabaseException("Not Connected to '$database'", 1);
	}

	/**
	 * Connect to MySQL database
	 * @return MySQL
	 */
	public static function connect($host, $user, $pwd, $db) {
		$instance = new MySQL($host, $user, $pwd, $db);
		self::$instances[$db] = $instance;
		self::$last_instance = $db;

	}


	private function __construct($host, $user, $pwd, $db) {
		$this->host = $host;
		$this->user = $user;
		$this->pwd  = $pwd;
		$this->db   = $db;
		$this->conn = mysql_connect($this->host, $this->user, $this->pwd);
		if ($error = mysql_error()) {
			throw new DatabaseException($error, 2);
		}
		mysql_select_db($this->db);
		if ($error = mysql_error()) {
			throw new DatabaseException($error, 3);
		}
		mysql_query("SET NAMES utf8", $this->conn);
		if ($error = mysql_error()) {
			throw new DatabaseException($error, 4);
		}
	}

	/**
	 * db queries counter
	 * @var int $count
	 */
	public static $count = 0;
	/**
	 * MySQL server host
	 * @var string $host
	 * MySQL server login
	 * @var string $user
	 * MySQL server password
	 * @var string $pwd
	 * MySQL database name
	 * @var string $db
	 */
	private $host, $user, $pwd, $db;

	/**
	 * MySQL connection resource
	 * @var mysqli $conn
	 * MySQL error message
	 * @var string $error
	 * last executed query
	 * @var $last
	 */
	public $conn, $error, $last;
	public $log = array();



	/**
	 * close connection to the database
	 * @return void
	 */
	public function close() {
		if (is_resource($this->conn)) {
			mysql_close($this->conn);
			if (defined('DEBUG') && DEBUG) {
				echo "\n<!--\n";
				print_r($this->log);
				echo "\n-->\n";
			}
		}
		unset(self::$instances[$this->db]);
	}

	public function __destruct() {
		$this->close();
	}


	/**
	 * Exectute query to the database
	 * @param string $sql
	 * @param array $data
	 * @return MySQL_Result Query result
	 */
	public function q($sql, $data=null) {
		// query parameters
		if (!empty($data)) {
			$data = array_reverse($data, true);
			$keys = array();
			$vals = array();
			foreach ($data as $key => $val) {
				$keys[] = ':' . $key;
				$vals[] = mysql_real_escape_string($val);
			}
			$sql = str_replace($keys, $vals, $sql);
		}

		// split queries by delimiter (may be several queries)
		$error = '';
		$res = null;
		foreach (explode(";;\n", $sql) as $q) {
			//execute query
			$res = mysql_query($q, $this->conn);
			// save an error message for debug purposes
			$error .= mysql_error($this->conn) . "\n";
			// inc query counter
			++self::$count;
		}
		if (defined('DEBUG')) {
			array_push($this->log, $sql);
			$error && array_push($this->log, $error);
		}

		return new MySQL_Result($res);
	}


	/**
	 * wrapper for q($sql) method
	 * @param string $sql
	 * @param array $data
	 * @return MySQL_Result Query result
	 */
	public function query($sql, $data=null) {
		return $this->q($sql, $data=null);
	}


	/**
	 * identifier of last inserted row
	 * @return int
	 */
	public function insertedId() {
		return mysql_insert_id($this->conn);
	}

	/**
	 * Connection status. True if connected.
	 * @return bool
	 */
	public function ok() {
		return is_resource($this->conn);
	}


	/**
	 * Connection status. True if connected.
	 * @return bool
	 */
	public function connected() {
		return is_resource($this->conn);
	}


	/**
	 * Delete row by $id from $table
	 * @param string $table
	 * @param int $id
	 * @return bool
	 */
	public function delete($table, $id) {
		return self::q("DELETE FROM `" . $table . "` WHERE `id`=':0'", array($id));
	}


}


/**
 * MySQLi result handler class
 */
class MySQL_Result extends Vector {

	/**
	 * number of result rows
	 * @var integer $num
	 */
	private $num = 0;
	/**
	 * cursor for rows
	 * @var integer $cursor
	 */
	private $cursor = 0;
	/**
	 * Cursor initiated flag
	 * @var bool
	 */
	private $init = false;

	/**
	 * class constructor.
	 * Determine query success. Fetch rows, if they exist.
	 * @param mysql_result $res
	 */
	public function __construct($res) {
		if (is_resource($res)) {
			for ($this->num = 0; $this->items[] = mysql_fetch_assoc($res); $this->num++) {
			}
			array_pop($this->items);
			mysql_free_result($res);
		} elseif ($res === true) {
			$this->items = true;
		}
	}


	/**
	 * wrapper for success() method. exists for compatibility.
	 * @return bool
	 */
	public function ready() {
		return $this->success();
	}

	/**
	 * check if query succeeded
	 * @return bool
	 */
	public function success() {
		if ($this->num || $this->items === true) {
			return true;
		} else {
			return false;
		}
	}


	/**
	 * check if row at cursor exists
	 * @return bool
	 */
	public function exists() {
		return $this->cursor < $this->num && $this->cursor >= 0;
	}


	/**
	 * Return result rows count
	 * @return int
	 */
	public function num() {
		return $this->num;
	}


	/**
	 * Return result row as numerated array
	 * @return array
	 */
	public function row() {
		$res = false;
		if ($this->exists()) {
			$res = array();
			foreach ($this->items[$this->cursor] as &$val) {
				$res[] = $val;
			}
		}
		++$this->cursor;
		return $res;
	}


	/**
	 * Return result row a associative array field => value
	 * @return assoc_array
	 */
	public function assoc() {
		if ($this->exists()) {
			return $this->items[$this->cursor++];
		} else {
			return false;
		}
	}


	/**
	 * Return $field value of current row
	 * @param string $field
	 * @return mixed
	 */
	public function get($field) {
		if ($this->exists()) {
			return $this->items[$this->cursor++][$field];
		}
		return false;
	}


	/**
	 * Return values of $field as array
	 * @param string $field
	 * @return array
	 */
	public function get_array($field) {
		if ($this->num) {
			$a = array();
			foreach ($this->items as $res) {
				$a[] = $res[$field];
			}
			return $a;
		} else {
			return false;
		}
	}


	/**
	 * Return rows or values of $field as array
	 * @param string[optional] $field
	 * @return array
	 */
	public function arr($field = null) {
		if ($this->num) {
			return $field ? $this->get_array($field) : $this->items;
		} else {
			return false;
		}
	}


	/**
	 * Switch to the next row
	 * @return MySQL_Result
	 */
	public function next() {
		if ($this->exists()) {
			if (!$this->init) {
				$this->init = true;
				if ($this->cursor === 0) {
					return $this;
				}
			}
			++$this->cursor;
		}
		return $this;
	}


	/**
	 * Switch to the previous row
	 * @return MySQL_Result
	 */
	public function prev() {
		if ($this->exists()) {
			--$this->cursor;
		}
		return $this;
	}


	/**
	 * Switch to the last row
	 * @return MySQL_Result
	 */
	public function last() {
		$this->cursor = $this->num - 1;
		return $this;
	}


	/**
	 * Switch to the first row
	 * @return MySQL_Result
	 */
	public function first() {
		!$this->init && $this->init = true;
		$this->cursor = 0;
		return $this;
	}


	/**
	 * Switch to the initial row
	 * @return MySQL_Result
	 */
	public function initial() {
		$this->init = false;
		$this->cursor = 0;
		return $this;
	}


	public function __get($name) {
		!$this->init && $this->init = true;
		return $this->exists() ? $this->items[$this->cursor][$name] : null;
	}

	public function __set($name, $value) {
		!$this->init && $this->init = true;
		return $this->items[$this->cursor][$name] = $value;
	}

}

